/**
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.oauth.jsontoken;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import net.oauth.jsontoken.crypto.HmacSHA256Signer;
import net.oauth.jsontoken.crypto.RsaSHA256Signer;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import org.joda.time.Duration;
import org.joda.time.Instant;
import java.security.SignatureException;
import java.util.regex.Pattern;
public class JsonTokenTest extends JsonTokenTestBase {
private static final Duration SKEW = Duration.standardMinutes(1);
public static String TOKEN_STRING = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIyfQ.jKcuP6BR_-cKpQv2XdFLguYgOxw4ahkZiqjcgrQcm70";
public static String TOKEN_STRING_ISSUER_NULL = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOm51bGwsImJhciI6MTUsImZvbyI6InNvbWUgdmFsdWUiLCJhdWQiOiJodHRwOi8vd3d3Lmdvb2dsZS5jb20iLCJpYXQiOjEyNzY2Njk3MjIsImV4cCI6MTI3NjY2OTcyMn0.jKcuP6BR_-cKpQv2XdFLguYgOxw4ahkZiqjcgrQcm70";
public static String TOKEN_STRING_BAD_SIG = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIyfQ.jKcuP6BR_";
public static String TOKEN_STRING_2PARTS = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIyfQ";
public static String TOKEN_STRING_EMPTY_SIG = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIyfQ.";
public static String TOKEN_STRING_CORRUPT_HEADER = "0yJ0bGci0iJIUzI0NiIsIm0pZCI60mtleT0ifQ.eyJpc3MiOiJnb29nbGUuY29tIiwiYmFyIjoxNSwiZm9vIjoic29tZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIyfQ.jKcuP6BR_-cKpQv2XdFLguYgOxw4ahkZiqjcgrQcm70";
public static String TOKEN_STRING_CORRUPT_PAYLOAD = "eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleTIifQ.eyJpc3&&&&&XtOiJnb290bGUuY20tIiwiYmFyIjoxNSwiZm9vIjoic290ZSB2YWx1ZSIsImF1ZCI6Imh0dHA6Ly93d3cuZ29vZ2xlLmNvbSIsImlhdCI6MTI3NjY2OTcyMiwiZXhwIjoxMjc2NjY5NzIyfQ.jKcuP6BR_-cKpQv2XdFLguYgOxw4ahkZiqjcgrQcm70";
public FakeClock clock = new FakeClock(SKEW);
@Override
public void setUp() throws Exception {
super.setUp();
clock.setNow(new Instant(1276669722000L));
}
public void testCreateJsonToken() throws Exception {
HmacSHA256Signer signer = new HmacSHA256Signer("google.com", "key2", SYMMETRIC_KEY);
JsonToken token = new JsonToken(signer, clock);
token.setParam("bar", 15);
token.setParam("foo", "some value");
token.setAudience("http://www.google.com");
token.setIssuedAt(clock.now());
token.setExpiration(clock.now().withDurationAdded(60, 1));
assertEquals(TOKEN_STRING, token.serializeAndSign());
}
private void checkTimeFrame(Instant issuedAt, Instant expiration) throws Exception {
HmacSHA256Signer signer = new HmacSHA256Signer("google.com", "key2", SYMMETRIC_KEY);
JsonToken token = new JsonToken(signer, clock);
if (issuedAt != null) {
token.setIssuedAt(issuedAt);
}
if (expiration != null) {
token.setExpiration(expiration);
}
token.setAudience("http://www.google.com");
JsonToken verifiedToken = new JsonTokenParser(clock, locators, new IgnoreAudience())
.verifyAndDeserialize(token.serializeAndSign());
assertEquals(issuedAt, verifiedToken.getIssuedAt());
assertEquals(expiration, verifiedToken.getExpiration());
}
private void checkTimeFrameIllegalStateException(Instant issuedAt, Instant expiration)
throws Exception {
try {
checkTimeFrame(issuedAt, expiration);
fail("IllegalStateException should be thrown.");
} catch (IllegalStateException e) {
// Pass.
}
}
public void testIssuedAtAfterExpiration() throws Exception {
Instant issuedAt = clock.now();
Instant expiration = issuedAt.minus(Duration.standardSeconds(1));
checkTimeFrameIllegalStateException(issuedAt, expiration);
}
public void testIssueAtSkew() throws Exception {
Instant issuedAt = clock.now().plus(SKEW.minus(Duration.standardSeconds(1)));
Instant expiration = issuedAt.plus(Duration.standardSeconds(1));
checkTimeFrame(issuedAt, expiration);
}
public void testIssueAtTooMuchSkew() throws Exception {
Instant issuedAt = clock.now().plus(SKEW.plus(Duration.standardSeconds(1)));
Instant expiration = issuedAt.plus(Duration.standardSeconds(1));
checkTimeFrameIllegalStateException(issuedAt, expiration);
}
public void testExpirationSkew() throws Exception {
Instant expiration = clock.now().minus(SKEW.minus(Duration.standardSeconds(1)));
Instant issuedAt = expiration.minus(Duration.standardSeconds(1));
checkTimeFrame(issuedAt, expiration);
}
public void testExpirationTooMuchSkew() throws Exception {
Instant expiration = clock.now().minus(SKEW.plus(Duration.standardSeconds(1)));
Instant issuedAt = expiration.minus(Duration.standardSeconds(1));
checkTimeFrameIllegalStateException(issuedAt, expiration);
}
public void testIssuedAtNull() throws Exception {
Instant expiration = clock.now().minus(SKEW.minus(Duration.standardSeconds(1)));
checkTimeFrame(null, expiration);
}
public void testExpirationNull() throws Exception {
Instant issuedAt = clock.now().plus(SKEW.minus(Duration.standardSeconds(1)));
checkTimeFrame(issuedAt, null);
}
public void testIssueAtNullExpirationNull() throws Exception {
checkTimeFrame(null, null);
}
public void testFutureToken() throws Exception {
Instant issuedAt = clock.now().plus(SKEW.plus(Duration.standardSeconds(1)));
Instant expiration = issuedAt.plus(Duration.standardDays(1));
checkTimeFrameIllegalStateException(issuedAt, expiration);
}
public void testPastToken() throws Exception {
Instant expiration = clock.now().minus(SKEW.plus(Duration.standardSeconds(1)));
Instant issuedAt = expiration.minus(Duration.standardDays(1));
checkTimeFrameIllegalStateException(issuedAt, expiration);
}
public void testJsonNull() throws Exception {
JsonTokenParser parser = new JsonTokenParser(null, null);
JsonToken token = parser.deserialize(TOKEN_STRING_ISSUER_NULL);
assertNull(token.getIssuer());
}
public void testDeserializeInvalidToken() throws Exception {
JsonTokenParser parser = new JsonTokenParser(clock, locators, new IgnoreAudience());
JsonToken token1 = parser.deserialize(TOKEN_STRING_BAD_SIG);
deserializeAndExpectIllegalArgument(parser, TOKEN_STRING_2PARTS);
deserializeAndExpectIllegalArgument(parser, TOKEN_STRING_EMPTY_SIG);
}
public void testDeserializeCorruptJson() throws Exception {
JsonTokenParser parser = new JsonTokenParser(clock, locators, new IgnoreAudience());
try {
parser.deserialize(TOKEN_STRING_CORRUPT_HEADER);
fail("Expected JsonParseException");
} catch (JsonParseException e) {
// no-op
}
try {
parser.deserialize(TOKEN_STRING_CORRUPT_PAYLOAD);
fail("Expected JsonParseException");
} catch (JsonParseException e) {
// no-op
}
}
private void deserializeAndExpectIllegalArgument(JsonTokenParser parser,
String tokenString) throws SignatureException {
try {
parser.deserialize(tokenString);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// no-op
} catch (IllegalStateException e) {
// no-op
}
}
public void testDeserialize() throws Exception {
JsonTokenParser parser = new JsonTokenParser(clock, locators, new IgnoreAudience());
JsonToken token = parser.deserialize(TOKEN_STRING);
assertEquals("google.com", token.getIssuer());
assertEquals(15, token.getParamAsPrimitive("bar").getAsLong());
assertEquals("some value", token.getParamAsPrimitive("foo").getAsString());
}
public void testVerifyAndDeserialize() throws Exception {
JsonTokenParser parser = new JsonTokenParser(clock, locators, new IgnoreAudience());
JsonToken token = parser.verifyAndDeserialize(TOKEN_STRING);
assertEquals("google.com", token.getIssuer());
assertEquals(15, token.getParamAsPrimitive("bar").getAsLong());
assertEquals("some value", token.getParamAsPrimitive("foo").getAsString());
}
public static String TOKEN_FROM_RUBY = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8";
public void testVerificationOnTokenFromRuby() throws Exception {
JsonTokenParser parser = new JsonTokenParser(clock, locatorsFromRuby, new IgnoreAudience());
JsonToken token = parser.verifyAndDeserialize(TOKEN_FROM_RUBY);
}
public void testCreateAnotherJsonToken() throws Exception {
HmacSHA256Signer signer = new HmacSHA256Signer(null, (String) null, "secret".getBytes());
JsonToken token = new JsonToken(signer, clock);
token.setParam("hello", "world");
String encodedToken = token.serializeAndSign();
}
public void testPublicKey() throws Exception {
RsaSHA256Signer signer = new RsaSHA256Signer("google.com", "key1", privateKey);
JsonToken token = new JsonToken(signer, clock);
token.setParam("bar", 15);
token.setParam("foo", "some value");
token.setExpiration(clock.now().withDurationAdded(60, 1));
String tokenString = token.serializeAndSign();
assertNotNull(token.toString());
JsonTokenParser parser = new JsonTokenParser(clock, locators, new IgnoreAudience());
token = parser.verifyAndDeserialize(tokenString);
assertEquals("google.com", token.getIssuer());
assertEquals(15, token.getParamAsPrimitive("bar").getAsLong());
assertEquals("some value", token.getParamAsPrimitive("foo").getAsString());
// now test what happens if we tamper with the token
JsonObject payload = new JsonParser().parse(
StringUtils.newStringUtf8(Base64.decodeBase64(tokenString.split(Pattern.quote("."))[1])))
.getAsJsonObject();
payload.remove("bar");
payload.addProperty("bar", 14);
String payloadString = new Gson().toJson(payload);
String[] parts = tokenString.split("\\.");
parts[1] = Base64.encodeBase64URLSafeString(payloadString.getBytes());
assertEquals(3, parts.length);
String tamperedToken = parts[0] + "." + parts[1] + "." + parts[2];
try {
token = parser.verifyAndDeserialize(tamperedToken);
fail("verification should have failed");
} catch (SignatureException e) {
// expected
}
}
}